package quota

import (
	"fmt"
	"math"
	"sync"
	"sync/atomic"
	"time"

	"go-common/library/rate/limit/quota/rate"
)

var (
	waiters               = make(map[string]Waiter)
	waiterLock            sync.RWMutex
	_defaultWaiterRefSecs = int64(10)
)

type Waiter interface {
	// 等待资源释放1个额度
	Wait()
	// 等待资源释放n个额度
	WaitN(n int)
	// 是否为注册资源
	UnknowResource() bool
	Close()
}

type WaiterConfig struct {
	// quota平台申请的资源ID
	ID string
	// 是否禁用限流
	Disable bool
	// 异步初始化 默认false 如果置为true则未拉取到资源时不限流
	Async bool
	// 异步初始化 是否允许未注册资源
	AsyncNotAllowStranger bool
}

type waiter struct {
	*resource
}

func NewWaiter(cfg *WaiterConfig) (w Waiter) {
	if cfg == nil {
		panic("quota: waiter config is nil")
	}
	if cfg.Disable {
		return &disbledWaiter{}
	}
	waiterLock.Lock()
	defer waiterLock.Unlock()
	w = waiters[cfg.ID]
	if w == nil {
		var err error
		if w, err = newWaiter(cfg.ID, cfg.Async, cfg.AsyncNotAllowStranger); err != nil {
			panic(err)
		}
		waiters[cfg.ID] = w
	}
	return
}

func newWaiter(id string, async bool, asyncNotAllowStranger bool) (w Waiter, err error) {
	r := &resource{
		id:              id,
		refreshInterval: _defaultWaiterRefSecs,
		ripeTime:        time.Now(),
		closCh:          make(chan bool),
		updateCh:        make(chan float64, 1),
		unknow:          true,
	}
	w = &waiter{
		resource: r,
	}
	if async {
		if asyncNotAllowStranger {
			r.lm = rate.NewLimiter(0, 1)
		} else {
			r.lm = rate.NewLimiter(rate.Inf, math.MaxInt64)
		}
		go r.refreshResource(3, 0)
	} else {
		if err = r.refreshResource(3, 0); err != nil {
			err = fmt.Errorf("quota: new waiter(%s) err: %v", id, err)
			w = nil
			return
		}
	}
	go r.tickerproc()
	go r.syncerproc()
	return
}

func (w *waiter) Wait() {
	w.WaitN(1)
}

func (w *waiter) WaitN(n int) {
	for {
		burst := w.lm.Burst()
		limit := w.lm.Limit()
		chunk := n
		if (n > burst) && (limit != rate.Inf) {
			chunk = burst
		}
		idx := w.idx()
		atomic.AddInt64(&w.requests[idx], int64(chunk))
		get := w.lm.WaitN(chunk)
		atomic.AddInt64(&w.used[w.idx()], int64(get))
		if n -= get; n <= 0 {
			break
		}
	}
}

func (w *waiter) UnknowResource() bool {
	return w.unknow
}

type disbledWaiter struct{}

func (w *disbledWaiter) Wait()                {}
func (w *disbledWaiter) WaitN(n int)          {}
func (w *disbledWaiter) Close()               {}
func (w *disbledWaiter) UnknowResource() bool { return false }
